package org.example.aienglishapp.service.login;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.Date;
import java.util.function.Function;

/**
 * JWT工具类。
 *
 * 该类提供了JWT（JSON Web Token）的生成、解析和验证功能，用于用户认证和其他安全相关的操作。
 *
 * @author xlj
 * @since 2024-10-1
 * @version 1.0
 */
@Service
public class JwtUtil {

    /**
     * JWT签名密钥。
     */
    private final SecretKey secret = Keys.hmacShaKeyFor(Decoders.BASE64.decode("evHuqJjLAPuWbYzk0912sjNqbt3rrV7SOphqOiXpNLjWZJPqKX5nDvueGAhyDtPKf2SQZyd4j/eD0d4dXIJQ2g=="));

    /**
     * JWT过期时间，单位为毫秒，默认设置为10小时。
     */
    private final long expiration = 1000 * 60 * 60 * 10;

    /**
     * 根据用户名生成JWT令牌。
     *
     * @param username 用户名
     * @return 返回生成的JWT令牌字符串
     */
    public String generateToken(String username) {
        Instant now = Instant.now();
        return Jwts.builder()
                .subject(username)
                .issuedAt(Date.from(now))
                .expiration(Date.from(now.plusMillis(expiration)))
                .signWith(secret)
                .compact();
    }

    /**
     * 验证JWT令牌的有效性。
     *
     * @param token        要验证的JWT令牌字符串
     * @param userDetails  当前用户详情对象
     * @return 如果令牌有效且用户名匹配则返回 true，否则返回 false
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    /**
     * 从JWT令牌中提取用户名。
     *
     * @param token 要解析的JWT令牌字符串
     * @return 返回解析出的用户名
     */
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    /**
     * 从JWT令牌中提取过期日期。
     *
     * @param token 要解析的JWT令牌字符串
     * @return 返回解析出的过期日期
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    /**
     * 从JWT令牌中提取指定的声明信息。
     *
     * @param <T>               声明信息的类型
     * @param token             要解析的JWT令牌字符串
     * @param claimsResolver    解析Claims的方法引用或lambda表达式
     * @return 返回解析出的声明信息
     */
    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    /**
     * 解析JWT令牌并获取所有声明信息。
     *
     * @param token 要解析的JWT令牌字符串
     * @return 返回包含所有声明信息的Claims对象
     */
    private Claims getAllClaimsFromToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(secret)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
        } catch (SignatureException e) {
            throw new RuntimeException("无效的JWT签名", e);
        } catch (Exception e) {
            throw new RuntimeException("解析令牌失败", e);
        }
    }

    /**
     * 检查JWT令牌是否已过期。
     *
     * @param token 要检查的JWT令牌字符串
     * @return 如果令牌已过期返回 true，否则返回 false
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(Date.from(Instant.now()));
    }
}